package com.xuecheng.manage_cms.service;

import com.alibaba.fastjson.JSON;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.CmsTemplate;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.domain.cms.response.CmsPageResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.*;
import com.xuecheng.framework.utils.SysUtil;
import com.xuecheng.manage_cms.config.RabbitmqConfig;
import com.xuecheng.manage_cms.dao.CmsPageRepository;
import com.xuecheng.manage_cms.dao.CmsTemplateRepository;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.bson.types.ObjectId;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.client.RestTemplate;


import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author gongfeiyue
 * @date 2020/5/18 11:26
 * @Description
 */
@Service
@Slf4j
public class PageService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CmsPageRepository cmsPageRepository;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private CmsTemplateRepository cmsTemplateRepository;
    @Autowired
    private GridFsTemplate gridFsTemplate;
    @Autowired
    private GridFSBucket gridFSBucket;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     *  页面列表分页查询方法
     *  @param page 当前页码
     *  @param size 页面显示个数
     *  @param queryPageRequest 查询条件
     *  @return 页面列表
     */
    public QueryResponseResult findList(int page, int size, QueryPageRequest queryPageRequest) {
        if (queryPageRequest == null) {
            queryPageRequest = new QueryPageRequest();
        }
        if (page <= 0) {
            page = 1;
        }
        page = page - 1;//为了适应mongodb的接口将页码减1
        if (size <= 0) {
            size = 20;
        }
        // 条件匹配器
        ExampleMatcher exampleMatcher = ExampleMatcher.matching().withMatcher("pageAliase", ExampleMatcher.GenericPropertyMatchers.contains());
        // 页面别名
        CmsPage cmsPage = new CmsPage();
        // 站点id
        if (StringUtils.isNotEmpty(queryPageRequest.getSiteId())) {
            cmsPage.setSiteId(queryPageRequest.getSiteId());
        }
        if (StringUtils.isNotEmpty(queryPageRequest.getPageAliase())) {
            cmsPage.setPageAliase(queryPageRequest.getPageAliase());
        }
        // 条件查询
        Example<CmsPage> example = Example.of(cmsPage, exampleMatcher);
        //分页对象
        Pageable pageable = new PageRequest(page, size);
        //分页查询
        Page<CmsPage> all = cmsPageRepository.findAll(example, pageable);//自定义条件查询
        QueryResult<CmsPage> cmsPageQueryResult = new QueryResult<CmsPage>();
        cmsPageQueryResult.setList(all.getContent());       //数据列表
        cmsPageQueryResult.setTotal(all.getTotalElements());  //数据总记录数

        //返回结果
        QueryResponseResult queryResponseResult = new QueryResponseResult(CommonCode.SUCCESS, cmsPageQueryResult);
        return queryResponseResult;
    }

    /**
     * @Author: gongfeiyue
     * @Date: 2020/7/7 14:32
     * @Description: 页面保存方法 1.新增前需要校验页面是否存在
     **/
    public CmsPageResult add(CmsPage cmsPage) {

        CmsPage cmsPageFind = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());

        if (cmsPageFind != null) {
            ExceptionCast.cast(CommonCode.CMS_ADDPAGE_EXISTS);
            // throw new RuntimeException("页面已经存在，请检查数据！");  todo 自定义抛错信息怎么整？？
        }
        cmsPage.setPageId(null); //todo 主键由springdata自动生成，后面了解自动生成原理机制   是由mongodb完成的
        cmsPageRepository.save(cmsPage); // todo 区别方法save()和insert()
        return new CmsPageResult(CommonCode.SUCCESS, cmsPage);
//    return new CmsPageResult(CommonCode.FAIL,null); // todo 失败为什么要传null？  原因：添加失败 响应的新页面就应为空
    }

    /**
     * @Author: gongfy
     * @Date: 2020/7/8 15:11
     * @Description: 根据id查询页面信息
     **/
    public CmsPage findCmsPageById(String pageId) {

        Optional<CmsPage> cmsPageOptional = cmsPageRepository.findById(pageId);
        if (!cmsPageOptional.isPresent()) {
            ExceptionCast.cast(CommonCode.CMSPAGE_NOTEXISTS);
        }
        return cmsPageOptional.get();
    }

    /**
     * @Author: gongfy
     * @Date: 2020/7/8 15:04
     * @Description: 页面修改方法  1.根据id查询 2.set新值 3.保存cmspage
     **/
    public CmsPageResult modify(String pageId, CmsPage cmsPage) {
        CmsPage one = this.findCmsPageById(pageId);
        if (one != null) { // 根据id查询出不为空，则有值需要修改
            //更新模板id
            one.setTemplateId(cmsPage.getTemplateId());
            //更新所属站点
            one.setSiteId(cmsPage.getSiteId());
            //更新页面别名
            one.setPageAliase(cmsPage.getPageAliase());
            //更新页面名称
            one.setPageName(cmsPage.getPageName());
            //更新访问路径
            one.setPageWebPath(cmsPage.getPageWebPath());
            //更新物理路径
            one.setPagePhysicalPath(cmsPage.getPagePhysicalPath());
            //更新数据url
            one.setDataUrl(cmsPage.getDataUrl());
            //保存
            CmsPage save = cmsPageRepository.save(one); //todo 保存的one和返回的save有什么区别？
            if (save != null) { //修改成功 返回
                CmsPageResult cmsPageResult = new CmsPageResult(CommonCode.SUCCESS, save);
                log.info("保存后的返回值：" + save + "******************************修改的值：" + one);
                log.info("是否相等：" + save.equals(one)); // true
                return cmsPageResult;
            }
        }
        return new CmsPageResult(CommonCode.FAIL, null);
    }

    /**
     * @Author: gongfy
     * @Param: pageId
     * @Return: CmsPageResult
     * @Date: 2020/7/8 19:23
     * @Description: 页面删除方法 1.根据id查询 2.有删除 没有不删
     **/
    public ResponseResult deleteById(String pageId) {

        CmsPage cmsPage = this.findCmsPageById(pageId);
        if (cmsPage != null) {
            cmsPageRepository.deleteById(pageId);
            return new ResponseResult(CommonCode.SUCCESS);
        }
        return new ResponseResult(CommonCode.FAIL);
    }


//  1、填写页面DataUrl
//  在编辑cms页面信息界面填写DataUrl，将此字段保存到cms_page集合中。
//          2、静态化程序获取页面的DataUrl
//3、静态化程序远程请求DataUrl获取数据模型。
//          4、静态化程序获取页面的模板信息
//5、执行页面静态化

    /**
     * @Author: gongfy
     * @Param: pageId
     * @Return:
     * @Date: 2020/7/12 11:07
     * @Description: 获取静态化页面方法
     **/
    public String getPageHtml(String pageId) {

        // 1.静态化程序远程请求DataUrl获取数据模型  model
        Map model = this.getModelByPageId(pageId);
        // 2.静态化程序获取页面的模板信息  template
        String template = this.getTemplateByPageId(pageId);
        // 3.执行页面静态化 调用freemaker的方法执行静态化  model+template
        String html = this.generalHtml(model, template);
        if (html == null) {
            ExceptionCast.cast(CommonCode.CMS_GENERATEHTML_HTMLISNULL);
        }
        return html;
    }

    // 1.静态化程序远程请求DataUrl获取数据模型  model。
    public Map getModelByPageId(String pageId) {
        CmsPage cmsPage = this.findCmsPageById(pageId);
        String dataUrl = cmsPage.getDataUrl();
        if (dataUrl == null) {
            ExceptionCast.cast(CommonCode.CMSPAGE_DATAURL_NOTEXISTS);
        }
        //远程请求DataUrl获取数据模型  model  dataUrl:http://localhost:31001/cms/config/getModel/5a791725dd573c3574ee333f
        ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl, Map.class);
        Map model = forEntity.getBody();
        return model;
    }

    // 2.静态化程序获取页面的模板信息  template
    public String getTemplateByPageId(String pageId) {

        CmsPage cmsPage = this.findCmsPageById(pageId);
        String templateId = cmsPage.getTemplateId();
        if (templateId == null) {
            ExceptionCast.cast(CommonCode.CMSPAGE_TEMPLATEID_NOTEXISTS);
        }

        Optional<CmsTemplate> cmsTemplateOptional = cmsTemplateRepository.findById(templateId);
        if (cmsTemplateOptional.isPresent()) {
            CmsTemplate cmsTemplate = cmsTemplateOptional.get();
            // 从GridFs取出模板文件内容  在fs.files 中查询
            GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(cmsTemplate.getTemplateFileId())));
            //打开下载流对象
            GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
            //创建GridFsResource
            GridFsResource gridFsResource = new GridFsResource(gridFSFile, gridFSDownloadStream);

            try {
                String templateContent = IOUtils.toString(gridFsResource.getInputStream(), "utf-8");
                return templateContent;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    // 3.执行页面静态化 调用freemaker的方法执行静态化  model+template
    public String generalHtml(Map model, String templateContent) {
        try {
            //生成配置类
            Configuration configuration = new Configuration(Configuration.getVersion());
            //创建模板加载器
            StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
            stringTemplateLoader.putTemplate("template", templateContent);
            //配置模板加载器
            configuration.setTemplateLoader(stringTemplateLoader);
            //获取模板
            Template template = configuration.getTemplate("template");
            String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
            return html;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
    * @Author: gongfy
    * @Param: pageId 页面Id
    * @Date: 2020/7/22 10:12
    * @Description: 页面发布方法（MQ生产者） ：将html文件保存到服务器的指定目录
    * 1、cms页面发布接口执行页面静态化
    * 2、并将静态化页面存储至GridFS中
    * 3、静态化成功后，向消息队列发送页面发布的消息。 1）获取页面的信息及页面所属站点ID。 2）设置消息内容为页面ID。（采用json格式，方便日后扩展） 3）发送消息给ex_cms_postpage交换机，并将站点ID作为routingKey。
    **/
    public ResponseResult pagePublish(String pageId) {
        //1.执行静态化
        String pageHtml = this.getPageHtml(pageId);
        if (pageHtml == null){
            ExceptionCast.cast(CommonCode.CMS_GENERATEHTML_HTMLISNULL);
        }
        //2.保存静态化文件
        CmsPage cmsPage = saveHtml(pageId, pageHtml);
        //3.发送消息
        this.sendPostPage(pageId);
        return new ResponseResult(CommonCode.SUCCESS);
    }

    //2.保存静态页面内容  todo 返回值的作用是啥
    public CmsPage saveHtml(String pageId,String pageHtml){
        CmsPage cmsPage = this.findCmsPageById(pageId);
        String fileId = cmsPage.getHtmlFileId();
        //存储之前先删除 todo 为什么？ 删除的是哪一张表的数据
        if (StringUtils.isNotEmpty(fileId)){
            gridFsTemplate.delete(Query.query(Criteria.where("_id").is(fileId)));
        }
        //保存html文件到GridFS  todo IOUtils使用   copy()  toInputStream()
        InputStream inputStream = IOUtils.toInputStream(pageHtml);
        ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());

        //将文件id存储到cmspage中
        cmsPage.setHtmlFileId(objectId.toString());
        cmsPageRepository.save(cmsPage);
        return cmsPage;
    }

    //3.发送页面发布消息
    public void sendPostPage(String pageId){
        CmsPage cmsPage = this.findCmsPageById(pageId);
        //1.站点id为routingkey
        String siteId = cmsPage.getSiteId();
        System.out.println("routingKey:"+siteId+" 是否一致 "+siteId.equals("5a751fab6abb5044e0d19ea1"));
        //2.消息内容  Json类型的pageId
        Map<String,String> msgMap = new HashMap<>();
        msgMap.put("pageId",pageId);
        String msg = JSON.toJSONString(msgMap);
        //3.页面发布发送消息
        rabbitTemplate.convertAndSend(RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE,siteId,msg);
    }

    //页面保存
    public CmsPageResult savePage(CmsPage cmsPage) {
        //保存之前查询，如果有就跟新，如果没有就保存
        CmsPage cmsPageResult = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(
                cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
        if (cmsPageResult == null){
            return this.add(cmsPage);// 新增
        }
        return this.modify(cmsPageResult.getPageId(),cmsPage);//更新
    }
}
